package cn.lijiajia3515.cairo.auth.framework;

import cn.lijiajia3515.cairo.auth.domain.mongo.AccountMongo;
import cn.lijiajia3515.cairo.auth.domain.mongo.AuthMongoConstants;
import cn.lijiajia3515.cairo.auth.domain.mongo.ResourceMongo;
import cn.lijiajia3515.cairo.auth.domain.mongo.RoleMongo;
import cn.lijiajia3515.cairo.domain.CairoAccount;
import cn.lijiajia3515.cairo.domain.CairoDepartment;
import cn.lijiajia3515.cairo.domain.CairoRole;
import cn.lijiajia3515.cairo.mongo.MongoConstants;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

public class AccountRepository {
	private final MongoTemplate mongoTemplate;

	public AccountRepository(MongoTemplate mongoTemplate) {
		this.mongoTemplate = mongoTemplate;
	}

	@Cacheable(cacheNames = "security", key = "'account:' + #appId +':' + #id", condition = "#appId != null && #id != null")
	public CairoAccount getAccount(String appId, String id) {
		Criteria criteria = Criteria.where(AccountMongo.FIELD.META.DELETED).is(MongoConstants.NO_DELETED_VALUE).and(AccountMongo.FIELD.ID).is(id);
		Query query = Query.query(criteria);
		return Optional.ofNullable(mongoTemplate.findOne(query, AccountMongo.class, AuthMongoConstants.Collection.ACCOUNT))
			.map(d -> account(appId, d))
			.orElse(null);
	}

	@Cacheable(value = "security", key = "'authority:' + #appId + ':' +#id", condition = "#appId != null && #id != null")
	public Collection<? extends GrantedAuthority> getAuthority(String appId, String id) {
		Criteria criteria = Criteria.where(AccountMongo.FIELD.META.DELETED).is(MongoConstants.NO_DELETED_VALUE)
			.and(AccountMongo.FIELD.ID).is(id);
		Query query = Query.query(criteria);

		return Optional.ofNullable(mongoTemplate.findOne(query, AccountMongo.class, AuthMongoConstants.Collection.ACCOUNT))
			.map(d -> {
				Set<String> authorities = new HashSet<>() {{
					add("account");
				}};
				// 角色权限
				authorities.addAll(Optional.ofNullable(d.getRole()).orElse(Collections.emptyMap()).getOrDefault(appId, Collections.emptySet()).stream().map("ROLE_"::concat).collect(Collectors.toSet()));
				// 账号权限
				authorities.addAll(Optional.ofNullable(d.getAuthority()).orElse(Collections.emptyMap()).getOrDefault(appId, Collections.emptySet()));

				Boolean adminAccount = d.getAccountAdmin().getOrDefault(appId, false);
				if (adminAccount) authorities.add("admin");

				Set<String> resourceIds = new HashSet<>(Optional.ofNullable(d.getResource()).orElse(Collections.emptyMap()).getOrDefault(appId, Collections.emptySet()));

				Criteria roleCriteria = Criteria.where(RoleMongo.FIELD.META.DELETED).and(RoleMongo.FIELD.CLIENT).is(appId);
				if (!adminAccount) {
					roleCriteria.and(RoleMongo.FIELD.ID).in(Optional.ofNullable(d.getRole()).orElse(Collections.emptyMap()).getOrDefault(appId, Collections.emptySet()));
				}
				Query roleQuery = Query.query(roleCriteria);
				roleQuery.fields().include(RoleMongo.FIELD.RESOURCES);

				Set<String> roleResourceIds = mongoTemplate.find(roleQuery, RoleMongo.class, AuthMongoConstants.Collection.ROLE)
					.stream().flatMap(x -> Optional.ofNullable(x.getResources()).stream().flatMap(Collection::stream))
					.collect(Collectors.toSet());
				resourceIds.addAll(roleResourceIds);

				Criteria resourceCriteria = Criteria
					.where(ResourceMongo.FIELD.META.DELETED)
					.and(ResourceMongo.FIELD.CLIENT).is(appId)
					.and(ResourceMongo.FIELD.ID).in(resourceIds);

				Query resourceQuery = Query.query(resourceCriteria);
				resourceQuery.fields().include(ResourceMongo.FIELD.PERMISSIONS);

				Set<String> permissions = mongoTemplate.find(resourceQuery, ResourceMongo.class, AuthMongoConstants.Collection.RESOURCE)
					.stream().flatMap(x -> Optional.ofNullable(x.getPermissions()).stream().flatMap(Collection::stream))
					.filter(x -> x != null && !x.isBlank())
					.collect(Collectors.toSet());

				// 角色下级权限
				authorities.addAll(permissions);
				return authorities;
			})
			.map(x -> x.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()))
			.orElse(null);
	}

	/**
	 * 构建账号信息
	 *
	 * @param client  应用端
	 * @param account 账号
	 * @return 账号
	 */
	protected CairoAccount account(String client, AccountMongo account) {
		Set<String> roleIds = Optional.ofNullable(account.getRole()).orElse(Collections.emptyMap()).getOrDefault(client, Collections.emptySet());
		Set<String> departmentIds = Optional.ofNullable(account.getDepartment()).orElse(Collections.emptyMap()).getOrDefault(client, Collections.emptySet());

		return CairoAccount.builder()
			.id(account.getId())

			.login(account.getUsername())
			.phoneNumber(account.getPhoneNumber())
			.email(account.getEmail())

			.nickname(account.getName())
			.avatarUrl(account.getAvatarUrl())
			.roles(roleIds.stream().map(x -> CairoRole.builder().id(x).name(x).build()).collect(Collectors.toList()))
			.departments(departmentIds.stream().map(x -> CairoDepartment.builder().id(x).name(x).build()).collect(Collectors.toList()))

			.build();
	}
}
